package com.hyperfix.washer.service.impl;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hyperfix.common.config.RequestDataHelper;
import com.hyperfix.models.common.R;
import com.hyperfix.models.constant.common.WeekConstant;
import com.hyperfix.models.washer.dtos.*;
import com.hyperfix.models.washer.pojos.ParamsData;
import com.hyperfix.models.washer.pojos.Pipeline;
import com.hyperfix.models.washer.pojos.Record;
import com.hyperfix.models.washer.pojos.Station;
import com.hyperfix.models.washer.vos.RecordQueryVO;
import com.hyperfix.utils.common.TimeUtil;
import com.hyperfix.washer.mapper.PipelineMapper;
import com.hyperfix.washer.mapper.RecordMapper;
import com.hyperfix.washer.mapper.RiskRecordMapper;
import com.hyperfix.washer.mapper.StationMapper;
import com.hyperfix.washer.service.RecordService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

import static com.hyperfix.models.constant.common.ExcelConstant.ONCE_PAGE_SIZE;
import static com.hyperfix.utils.common.TimeUtil.DAY_NUM_OF_WEEK;
import static com.hyperfix.utils.common.TimeUtil.HOUR_NUM_OF_DAY;

/**
 * @author JiangChunYu
 * 2024/4/3
 */
@Slf4j
@Transactional
@Service
public class RecordServiceImpl extends ServiceImpl<RecordMapper, Record> implements RecordService {
    @Resource
    private RecordMapper recordMapper;
    @Resource
    private PipelineMapper pipelineMapper;
    @Resource
    private StationMapper stationMapper;
    @Resource
    private RiskRecordMapper riskRecordMapper;
    @Resource
    private ThreadPoolTaskExecutor taskExecutor;

    @Async
    @Override
    public void asyncSave(LocalDateTime time, ParamsData paramsData) {
        RequestDataHelper.setRequestData(paramsData.getPipelineId());
        Record record = new Record();
        BeanUtils.copyProperties(paramsData, record);
        record.setRecordTime(time);
        this.save(record);
    }

    @Override
    public IPage<RecordDTO> list(Integer pageNum, Integer size, RecordQueryVO recordQueryVO) {
        IPage<RecordDTO> recordDTOPage = new Page<>(pageNum, size);
        RequestDataHelper.setRequestData(recordQueryVO.getPipelineId());
        recordDTOPage = recordMapper.listRecordDTO(
                recordDTOPage,
                recordQueryVO.getStartRecordTime(),
                recordQueryVO.getEndRecordTime()
        );
        return recordDTOPage;
    }

    @Override
    public void exportExcel(RecordQueryVO recordQueryVO, HttpServletResponse response) {
        try {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            String strNow = TimeUtil.formatInline(LocalDateTime.now());
            String fileName = "历史数据" + strNow + ".xlsx"; // 文件名
            fileName = URLEncoder.encode(fileName, "UTF-8");
            response.setHeader("Content-disposition", "attachment; filename=" + fileName);
            response.setHeader("Access-Control-Expose-Headers", "Content-disposition");

            Integer pipelineId = recordQueryVO.getPipelineId();
            Pipeline pipeline = pipelineMapper.selectById(pipelineId);
            String sheetName = "管线-" + pipeline.getName() + "-历史数据"; // sheet名
            ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), RecordDTO.class)
                    .autoCloseStream(Boolean.FALSE)
                    .build();
            WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();
            RequestDataHelper.setRequestData(pipelineId);
            int count = recordMapper.countRecordDTO(recordQueryVO.getStartRecordTime(), recordQueryVO.getEndRecordTime()); // 总数据行数
            int pageCount = count % ONCE_PAGE_SIZE == 0 ? count / ONCE_PAGE_SIZE : count / ONCE_PAGE_SIZE + 1; // 页数
            // 分页多次写入
            for (int i = 0; i < pageCount; i++) {
                IPage<RecordDTO> recordDTOPage = new Page<>(i + 1, ONCE_PAGE_SIZE);
                recordDTOPage = recordMapper.listRecordDTO(
                        recordDTOPage,
                        recordQueryVO.getStartRecordTime(),
                        recordQueryVO.getEndRecordTime()
                );
                excelWriter.write(recordDTOPage.getRecords(), writeSheet);
            }
            excelWriter.finish();
        } catch (Exception e) {
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            R<String> r = R.error("历史数据导出失败");
            try {
                response.getWriter().println(JSON.toJSONString(r));
            } catch (IOException ex) {
                log.error(ex.getMessage());
            }
        }
    }

    @Override
    public Long statisticDayTotalFlow(Integer teamId) {
        LocalDate now = LocalDate.now();
        LocalDateTime startOfToday = LocalDateTime.of(now, LocalTime.MIN);
        LocalDateTime endOfToday = LocalDateTime.of(now, LocalTime.MAX);
        List<Pipeline> pipelines = pipelineMapper.selectAllByTeamId(teamId);
        return getDayTotalFlow(pipelines, startOfToday, endOfToday);
    }

    @Override
    public Long statisticWeekTotalFlow(Integer teamId) {
        List<Pipeline> pipelines = pipelineMapper.selectAllByTeamId(teamId);
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime startOfWeek = now.with(WeekFields.of(DayOfWeek.MONDAY, 7).dayOfWeek(), 1).with(LocalTime.MIN);
        int dayOfWeek = now.getDayOfWeek().getValue();
        List<Integer> dayList = new ArrayList<>(dayOfWeek);
        for (int i = 0; i < dayOfWeek; i++) {
            dayList.add(i);
        }
        AtomicLong weekTotalFlow = new AtomicLong(0L);
        CompletableFuture<Void> allFuture = CompletableFuture.allOf(
                dayList.stream()
                        .map(i -> {
                            LocalDateTime day = startOfWeek.plusDays(i);
                            LocalDateTime startOfDay = day.with(LocalTime.MIN);
                            LocalDateTime endOfDay = day.with(LocalTime.MAX);
                            return CompletableFuture
                                    .supplyAsync(() -> getDayTotalFlow(pipelines, startOfDay, endOfDay), taskExecutor)
                                    .thenAccept(weekTotalFlow::addAndGet);
                        })
                        .toArray(CompletableFuture[]::new)
        );
        allFuture.join();
        return weekTotalFlow.get();
    }

    @Override
    public List<ParamTypeStatistic> statisticWeekParamType(Integer teamId) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime startOfWeek = now.with(WeekFields.of(DayOfWeek.MONDAY, 7).dayOfWeek(), 1).with(LocalTime.MIN);
        LocalDateTime endOfToday = now.with(LocalTime.MAX);
        return recordMapper.statisticParamType(teamId, startOfWeek, endOfToday);
    }

    @Override
    public List<StationRiskRecord> statisticTeamRiskRecord(Integer teamId) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime startOfWeek = now.with(WeekFields.of(DayOfWeek.MONDAY, 7).dayOfWeek(), 1).with(LocalTime.MIN);
        LocalDateTime endOfToday = now.with(LocalTime.MAX);
        List<Station> stations = stationMapper.selectList(new LambdaQueryWrapper<Station>().eq(Station::getTeamId, teamId));
        List<StationRiskRecord> stationRiskRecordList = new ArrayList<>();
        stations.forEach(station -> {
            Integer riskRecordNum = riskRecordMapper.countByStationId(station.getId(), startOfWeek, endOfToday);
            StationRiskRecord stationRiskRecord = new StationRiskRecord();
            stationRiskRecord.setName(station.getName());
            stationRiskRecord.setValue(riskRecordNum);
            stationRiskRecordList.add(stationRiskRecord);
        });
        return stationRiskRecordList;
    }

    @Override
    public TeamTotalFlow teamWeekTotalFlow(Integer teamId) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime startOfWeek = now.with(WeekFields.of(DayOfWeek.MONDAY, 7).dayOfWeek(), 1).with(LocalTime.MIN);
        int dayOfWeek = now.getDayOfWeek().getValue();

        List<Pipeline> pipelines = pipelineMapper.selectAllByTeamId(teamId);

        TeamTotalFlow teamTotalFlow = new TeamTotalFlow();
        teamTotalFlow.setDataName("管线");

        List<String> dayNameList = getDayNameList();
        teamTotalFlow.setDayNameList(dayNameList);

        List<CompletableFuture<PipelineTotalFlow>> futures = pipelines.stream()
                .map(pipeline -> CompletableFuture.supplyAsync(() -> {
                    PipelineTotalFlow pipelineTotalFlow = new PipelineTotalFlow();
                    pipelineTotalFlow.setName(pipeline.getName());
                    pipelineTotalFlow.setStationId(pipeline.getStationId());
                    List<Integer> weekTotalFlow = getWeekTotalFlow(pipeline.getId(), startOfWeek, dayOfWeek);
                    pipelineTotalFlow.setWeekTotalFlow(weekTotalFlow);
                    return pipelineTotalFlow;
                }, taskExecutor))
                .collect(Collectors.toList());

        List<PipelineTotalFlow> pipelineTotalFlowList = futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());

        teamTotalFlow.setPipelineTotalFlowList(pipelineTotalFlowList);

        return teamTotalFlow;
    }

    @Override
    public TeamTotalFlowTrend dayTotalFlowTrend(Integer teamId) {
        TeamTotalFlowTrend teamTotalFlowTrend = new TeamTotalFlowTrend();
        teamTotalFlowTrend.setDataName("站场");

        List<String> timeNameList = getTimeNameList();
        teamTotalFlowTrend.setTimeNameList(timeNameList);

        List<StationTotalFlow> stationTotalFlowList = getStationTotalFlowList(teamId, timeNameList);
        teamTotalFlowTrend.setStationTotalFlowList(stationTotalFlowList);

        return teamTotalFlowTrend;
    }

    /**
     * 获取指定井队下所有站场的时间节点总流量列表
     *
     * @param teamId       团队 ID
     * @param timeNameList 时间列表
     * @return {@link List}<{@link StationTotalFlow}> 指定井队下所有站场的时间节点总流量列表
     */
    public List<StationTotalFlow> getStationTotalFlowList(Integer teamId, List<String> timeNameList) {
        List<Station> stations = stationMapper.selectList(new LambdaQueryWrapper<Station>().eq(Station::getTeamId, teamId));

        List<CompletableFuture<StationTotalFlow>> futures = stations.stream()
                .map(station -> CompletableFuture.supplyAsync(() -> {
                    StationTotalFlow stationTotalFlow = new StationTotalFlow();
                    stationTotalFlow.setStationName(station.getName());
                    List<Pipeline> pipelines = pipelineMapper.selectList(new LambdaQueryWrapper<Pipeline>().eq(Pipeline::getStationId, station.getId()));
                    List<Long> timeNodeTotalFlowList = createInitTimeNodeTotalFlowList();
                    calcStationTotalFlow(timeNameList, pipelines, timeNodeTotalFlowList);
                    stationTotalFlow.setTimeNodeTotalFlowList(timeNodeTotalFlowList);
                    return stationTotalFlow;
                }, taskExecutor))
                .collect(Collectors.toList());

        return futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());
    }

    /**
     * 获取指定日期各管线日累计流量的总和
     *
     * @param pipelines    管线列表
     * @param startOfToday 指定日期的开始时间
     * @param endOfToday   指定日期的结束时间
     * @return long 各管线日累计流量的总和
     */
    public long getDayTotalFlow(List<Pipeline> pipelines, LocalDateTime startOfToday, LocalDateTime endOfToday) {
        AtomicLong dayTotalFlow = new AtomicLong(0);
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(
                pipelines.stream()
                        .map(pipeline -> CompletableFuture.supplyAsync(() -> {
                                    RequestDataHelper.setRequestData(pipeline.getId());
                                    return recordMapper.statisticDayTotalFlow(startOfToday, endOfToday);
                                }, taskExecutor)
                                .thenAccept(dayTotalFlow::addAndGet))
                        .toArray(CompletableFuture[]::new)
        );
        allFutures.join();
        return dayTotalFlow.get();
    }

    /**
     * 计算指定管线一周内每天的日累积流量
     *
     * @param pipelineId  管道 ID
     * @param startOfWeek 本周的开始时间
     * @param dayOfWeek   今天是本周的第几天
     * @return {@link List}<{@link Integer}> 一周内每天的日累积了流量
     */
    public List<Integer> getWeekTotalFlow(Integer pipelineId, LocalDateTime startOfWeek, int dayOfWeek) {
        List<CompletableFuture<Integer>> futures = new ArrayList<>();
        for (int i = 0; i < dayOfWeek; i++) {
            LocalDateTime day = startOfWeek.plusDays(i);
            LocalDateTime startOfDay = day.with(LocalTime.MIN);
            LocalDateTime endOfDay = day.with(LocalTime.MAX);
            CompletableFuture<Integer> future = CompletableFuture.supplyAsync(
                    () -> {
                        RequestDataHelper.setRequestData(pipelineId);
                        return recordMapper.getMaxTotalFlow(startOfDay, endOfDay);
                    },
                    taskExecutor
            );
            futures.add(future);
        }

        List<Integer> weekTotalFlow = futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());

        weekTotalFlow.addAll(Collections.nCopies(DAY_NUM_OF_WEEK - dayOfWeek, 0));

        return weekTotalFlow;
    }

    /**
     * 计算日期名称列表
     *
     * @return {@link List}<{@link String}> 星期一到星期天
     */
    private List<String> getDayNameList() {
        List<String> dayNameList = new ArrayList<>();
        for (int i = 1; i <= DAY_NUM_OF_WEEK; i++) {
            dayNameList.add(WeekConstant.WEEK_MAP.get(i));
        }
        return dayNameList;
    }

    /**
     * 生成时间列表 从今天的00:00到23:00 每隔1小时生成一个
     *
     * @return {@link List}<{@link String}> 今日时间列表
     */
    private List<String> getTimeNameList() {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime startOfDay = now.with(LocalTime.MIN);
        List<String> timeNameList = new ArrayList<>();
        for (int i = 0; i < HOUR_NUM_OF_DAY; i++) {
            timeNameList.add(TimeUtil.format(startOfDay.plusHours(i)));
        }
        return timeNameList;
    }

    /**
     * 创建初始化时间节点日累计流量列表
     *
     * @return {@link List}<{@link Long}> 初始化时间节点日累计流量列表
     */
    private List<Long> createInitTimeNodeTotalFlowList() {
        List<Long> timeNodeTotalFlowList = new ArrayList<>(HOUR_NUM_OF_DAY);
        for (int i = 0; i < HOUR_NUM_OF_DAY; i++) {
            timeNodeTotalFlowList.add(0L);
        }
        return timeNodeTotalFlowList;
    }

    /**
     * 计算某个站场下所有管线的时间节点日累计流量列表
     *
     * @param timeNameList          时间列表
     * @param pipelines             管线列表
     * @param timeNodeTotalFlowList 时间节点日累计流量列表
     */
    public void calcStationTotalFlow(List<String> timeNameList, List<Pipeline> pipelines, List<Long> timeNodeTotalFlowList) {
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(
                pipelines.stream()
                        .map(pipeline ->
                                CompletableFuture.supplyAsync(() -> {
                                            RequestDataHelper.setRequestData(pipeline.getId());
                                            return recordMapper.getDayTotalFlowList(timeNameList);
                                        }, taskExecutor)
                                        .thenAccept(flowList -> {
                                            for (int i = 0; i < flowList.size(); i++) {
                                                synchronized (timeNodeTotalFlowList.get(i)) {
                                                    timeNodeTotalFlowList.set(i, timeNodeTotalFlowList.get(i) + flowList.get(i));
                                                }
                                            }
                                        })
                        )
                        .toArray(CompletableFuture[]::new)
        );
        allFutures.join();
    }
}
